#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>


int thread_count, buffer_size, file_descriptor, run_threads, current_position = 0;
pthread_t * threads;
pid_t main_pid;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_exist = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_term = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t threads_running_cond;
int n;

void signal_handler(int sig_no) {
	if (sig_no == SIGUSR1) {
		printf("SIGUSR1 %i %u\n", getpid(), (unsigned int)pthread_self());
	} else if (sig_no == SIGTERM) {
		printf("SIGTERM %i %u\n", getpid(), (unsigned int)pthread_self());		
	} else if (sig_no == SIGKILL) {
		printf("SIGKILL %i %u\n", getpid(), (unsigned int)pthread_self());		
	} else if (sig_no == SIGSTOP) {
		printf("SIGSTOP %i %u\n", getpid(), (unsigned int)pthread_self());		
	} else {
		printf("signal %d %i %u\n", sig_no, getpid(), (unsigned int)pthread_self());		
	}
}

void clean(void * ptr) {
	printf("Zwalniam bufor clean %p\n", ptr);
	free((char *)ptr);
	pthread_mutex_lock(&mutex_exist);
	run_threads--;
	pthread_cond_signal(&threads_running_cond);
	pthread_mutex_unlock(&mutex_exist);
}

void * find(void * str) {
	sleep(1);
#if VERSION == 1
	if (pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL) != 0) {
		perror("pthread_setcanceltype");
		exit(1);
	}
#elif VERSION == 2
	if (pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL) != 0) {
		perror("pthread_setcanceltype");
		exit(1);
	}
#endif
#if defined TEST1 || defined TEST2
	sigset_t sigset;
	sigemptyset(&sigset);
	pthread_sigmask(SIG_SETMASK, &sigset, NULL);
#elif TEST3
	signal(SIGUSR1, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGKILL, signal_handler);
	signal(SIGSTOP, signal_handler);
#elif TEST4
	sigset_t sigset;
	sigemptyset(&sigset);
	sigaddset(&sigset, SIGUSR1);
	sigaddset(&sigset, SIGTERM);
	sigaddset(&sigset, SIGKILL);
	sigaddset(&sigset, SIGSTOP);
	pthread_sigmask(SIG_SETMASK, &sigset, NULL);
#elif TEST5
	sigset_t sigset;
	sigemptyset(&sigset);
	pthread_sigmask(SIG_SETMASK, &sigset, NULL);
	signal(SIGUSR1, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGKILL, signal_handler);
	signal(SIGSTOP, signal_handler);
#elif TEST6
	if (pthread_equal(threads[0], pthread_self())) {
		int tmp = 1/0;
		printf("%d\n", tmp);
	}
#endif
	char * string = (char *)str;
	char * buffer = (char *)malloc(sizeof(char) * buffer_size);
	printf("%p\n", buffer);
	#if VERSION < 3
	int i;
	#endif
	pthread_cleanup_push(clean, buffer);
	while (current_position < n-strlen(string)) {
		sleep(5);
		//printf("id:%d pos:%d n: %d\n", (int)pthread_self(), current_position, n);
		int my_position;
		if (pthread_mutex_lock(&mutex) != 0) {
			perror("mutex lock");
			exit(3);
		}
		my_position = current_position;
		lseek(file_descriptor, current_position, SEEK_SET);
		int result = read(file_descriptor, buffer, buffer_size);
		if (result == -1) {
			perror("read");
			exit(2);
		}
		current_position += result - strlen(string);
		if (pthread_mutex_unlock(&mutex) != 0) {
			perror("pthread_mutex_unlock");
			exit(4);
		}
		char * ptr = strstr(buffer, string);
		int pos = ptr - buffer;
		if (ptr != NULL) {
			if (pthread_mutex_lock(&mutex_term) != 0) {
				perror("pthread_mutex_lock");
				exit(5);
			}
			printf("%d: %d\n", (int)pthread_self(), my_position + pos);
		#if VERSION == 2
			if (pthread_mutex_unlock(&mutex_term) != 0) {
				perror("pthread_mutex_unlock");
				exit(6);
			}
		#endif
		#if VERSION < 3
			int tmp;
			if (pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &tmp) != 0) {
				perror("pthread_setcancelstate");
				exit(7);
			}
			for (i = 0; i < thread_count; i++) {
				if (pthread_self() != threads[i]) {
					if (pthread_cancel(threads[i]) != 0) {
						perror("pthread_cancel");
						exit(8);
					}
				}
			}
			if (pthread_setcancelstate(tmp, NULL) != 0) {
				perror("pthread_setcancelstate");
				exit(9);
			}
			pthread_cancel(pthread_self());
		#endif
		#if VERSION != 2
			if (pthread_mutex_unlock(&mutex_term) != 0) {
				perror("pthread_mutex_unlock");
				exit(10);
			}
		#elif VERSION == 2
			pthread_testcancel();
		#endif
		}
	}
	pthread_cleanup_pop(1);
	return NULL;
}


int main(int argc, char ** argv) {
	if (argc != 5) {
		printf("Niepoprawna liczba argumentów. Poprawne wywołanie: ./zad1.o <ilość_wątków> <nazwa_pliku> <rozmiar_buforu> <napis_do_wyszukania_w_pliku>\n");
		return -1;
	}
	run_threads = thread_count = atoi(argv[1]);
	if (thread_count <= 0) {
		printf("Liczba wątków musi być liczbą całkowitą większą od 0\n");
		return -2;
	}
	char * file_name = argv[2];
	buffer_size = atoi(argv[3]);
	if (buffer_size <= 0) {
		printf("Rozmiar buforu musi być liczbą całkowitą większą od 0\n");
		return -3;
	}
	char * string = argv[4];
	if (strlen(string) > buffer_size) {
		printf("Długość napisu nie może być większa od rozmiaru bufora\n");
		return -4;
	}
	file_descriptor = open(file_name, O_RDONLY);
	if (file_descriptor < 0) {
		perror(file_name);
		return -5;
	}	
	n = lseek(file_descriptor, 0, SEEK_END);
	lseek(file_descriptor, 0, SEEK_SET);
	main_pid = getpid();
	threads = (pthread_t *)malloc(sizeof(pthread_t)*thread_count);
#ifdef TEST1
	sigset_t sigset;
	sigemptyset(&sigset);
	sigprocmask(SIG_SETMASK, &sigset, NULL);
#elif TEST2
	sigset_t sigset;
	sigemptyset(&sigset);
	sigaddset(&sigset, SIGUSR1);
	sigaddset(&sigset, SIGTERM);
	sigaddset(&sigset, SIGKILL);
	sigaddset(&sigset, SIGSTOP);
	pthread_sigmask(SIG_SETMASK, &sigset, NULL);
	
#elif TEST3
	signal(SIGUSR1, signal_handler);
	signal(SIGTERM, signal_handler);
	signal(SIGKILL, signal_handler);
	signal(SIGSTOP, signal_handler);
#endif
	int i;
#if VERSION == 3
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	for(i = 0; i < thread_count; i++) {
		if (pthread_create(&(threads[i]), &attr, find, string) != 0) {
			perror("pthread_create");
			return -6;
		}
	}
	pthread_attr_destroy(&attr);
	pthread_mutex_lock(&mutex_exist);
	while (run_threads > 0) {
		pthread_cond_wait(&threads_running_cond, &mutex_exist);
	}
	pthread_mutex_unlock(&mutex_exist);
#else
	for(i = 0; i < thread_count; i++) {
		if (pthread_create(&(threads[i]), NULL, find, string) != 0) {
			perror("pthread_create");
			return -6;
		}
		printf("%d %d\n", i, (unsigned int)threads[i]);
	}
#ifdef TEST1
	sleep(1);
	raise(SIGSTOP);
#elif TEST2
	sleep(2);
	printf("Wysyłam sygnał\n");
	raise(SIGSTOP);
#elif TEST3
	sleep(5);
	raise(SIGSTOP);
#elif TEST4
	sleep(1);
	//pthread_kill(threads[0], SIGUSR1);
	//pthread_kill(threads[1], SIGTERM);
	//pthread_kill(threads[2], SIGKILL);
	pthread_kill(threads[3], SIGSTOP);
#elif TEST5
	sleep(1);
	//pthread_kill(threads[0], SIGUSR1);
	//pthread_kill(threads[1], SIGTERM);
	//pthread_kill(threads[2], SIGKILL);
	pthread_kill(threads[3], SIGSTOP);
#endif
	for (i = 0; i < thread_count; i++) {
		pthread_join(threads[i], NULL);
	}
#endif
	free(threads);
	return 0;
}
